#ifndef _RETRO_SPRITEEDITOR
#define _RETRO_SPRITEEDITOR

namespace Retro
{
  namespace Creator
  {
    namespace Editor
    {
      enum SpriteNotify
      {
        SpriteNotifySnap = MapNotifyUser,
      };

      enum SpriteTool
      {
        SpriteToolNone = MapToolNone,
        SpriteToolSelect = MapToolSelect,
        SpriteToolPen = MapToolCustom,
        SpriteToolRect,
        SpriteToolTile,
        SpriteToolLast = SpriteToolTile,
      };

      class SpriteEditor : public MapEditor
      {
      private:
        enum UndoAction
        {
          UndoNone,
          UndoAdd,
          UndoDelete,
          UndoInsert,
          UndoEdit,
        };

        struct SpriteCopy : Data::SpriteData
        {
          SpriteCopy*   next;
        };

        struct SpriteUndo
        {
          Device::Display::SpriteNode*  node;
          SpriteUndo*   next;
          SpriteUndo*   prev;
          SpriteUndo*   child;
          INT           layer;
          UndoAction    action;
          INT           flags;

          union Data
          {
            Device::Display::SpriteNode*  node;

            struct
            {
              Geom::Bounds  bounds;
              REAL          rotate;
            };
          }
          data;
        };

        Device::Display::SpriteNode** firstNode;
        Device::Display::SpriteNode*  deletedNode;
        Device::Display::SpriteNode*  mOvered;

        SpriteCopy*     copy;
        SpriteUndo*     undoNode;
        SpriteUndo*     undoCurr;
        SpriteUndo*     undoEdit;

        Anchor          resize;
        Geom::Point     mCurrPoint;
        Geom::Point     snapPoint;
        BOOL            mSnapToGrid;

        Device::Display::SpriteNode*  snapNodeX;
        Device::Display::SpriteNode*  snapNodeY;

        Data::SpriteType  mNodeType;
        Data::Resource*   mNodeResource;
        Geom::Bounds      mNodeSource;
        Geom::RGBColor    mNodeColors[Data::ColorLast - Data::ColorFirst + 1];

        VOID        addNode(
          const Geom::Bounds& source,
          const Geom::Bounds& dest
          );
        VOID        insertNode(
          Device::Display::SpriteNode* value,
          Device::Display::SpriteNode* after
          );
        VOID        removeNode(
          Device::Display::SpriteNode* value
          );
        VOID        selectNode(
          Device::Display::SpriteNode* value
          );
        VOID        deselectNode(
          Device::Display::SpriteNode* value
          );
        BOOL        hitTestNode(
          Device::Display::SpriteNode* value,
          const Geom::Point& pt
          );
        VOID        setSelectionFlags(
          INT set,
          INT unset
          );

        VOID        deleteUndo(
          SpriteUndo* value
          );

        SpriteUndo* createUndo(
          Device::Display::SpriteNode* value,
          UndoAction action
          );
        VOID        appendUndo(
          Device::Display::SpriteNode* value,
          UndoAction action
          );
        VOID        performUndo(
          SpriteUndo* value,
          BOOL redo
          );

        INL VOID    closeUndo()
        {
          undoEdit = NULL;
          getMap()->setUnsaved();
          sendNotify(EditorNotifyEdit);
          invalidate(FALSE);
        }

        VOID        calcEditBounds(
          Geom::Bounds &value,
          SelectMode mode);
        VOID        calcEditRotate(
          const Geom::Bounds &bounds,
          REAL &value,
          SelectMode mode);
        VOID        getTilePreview(Geom::Bounds &value);
        VOID        snapToGrid(Geom::Bounds &value);
        VOID        snapToNodes(Geom::Bounds &value);

        VOID        getAnchorBounds(
          Geom::Bounds &value,
          Anchor corner
          );
        VOID        drawNodeSelection();
        VOID        drawSelection();
        VOID        drawSnapGlyphs();
        VOID        drawAnchors();
        VOID        drawRotateGlyphs();

      protected:
        VOID        onPrepareDisplay();
        VOID        onInitializeDisplay();
        VOID        onUninitializeDisplay();
        VOID        drawForeground();
        VOID        drawLayer(INT layer);
        VOID        initialize();
        VOID        initToolbar();
        VOID        onToolbar(ToolBarNotify* iData);

        VOID        fillMapBuffer();
        VOID        freeMapBuffer();
        VOID        createMapBuffer();
        VOID        clearUndo(SpriteUndo* from);
        VOID        clearUndo();
        VOID        clearCopy();

        virtual VOID  onMouseDown(Event::MouseEvent& iEvent);
        virtual VOID  onMouseUp(Event::MouseEvent& iEvent);
        virtual VOID  onMouseMove(Event::MouseEvent& iEvent);
        virtual VOID  onMouseLeave();
        virtual VOID  onKeyDown(Event::KeyEvent& iEvent);
        virtual VOID  onFocus(Event::FocusEvent& iEvent);

      public:
        INL Data::Resource* getNodeResource()
        {
          return mNodeResource;
        }

        INL VOID    setNodeResource(Data::Resource* iValue)
        {
          mNodeResource = iValue;
        }

        INL const Geom::Bounds& getNodeSource()
        {
          return mNodeSource;
        }

        INL VOID    setNodeSource(const Geom::Bounds& iValue)
        {
          mNodeSource = iValue;
        }

        INL Data::SpriteType getNodeType()
        {
          return mNodeType;
        }

        INL VOID    setNodeType(Data::SpriteType iValue)
        {
          mNodeType = iValue;
        }

        INL COLOR   getNodeColor(Data::ColorIndex iIndex)
        {
          return mNodeColors[iIndex];
        }

        INL VOID    setNodeColor(COLOR iValue, Data::ColorIndex iIndex)
        {
          mNodeColors[iIndex].value = iValue;
        }

        INL VOID    setTool(SpriteTool value)
        {
          MapEditor::setTool((MapTool)value);
        }
        VOID        setLayer(INT value);

        INL BOOL    getGridSnap()
        {
          return mSnapToGrid;
        }
        VOID        setGridSnap(BOOL value);

        INL Data::SpriteData* getFirstSprite()
        {
          return firstNode ? firstNode[mLayer] : NULL;
        }
        INL Data::SpriteData* getNextSprite(Data::SpriteData* value)
        {
          return ((Device::Display::SpriteNode*)value)->next;
        }
        INL Data::SpriteData* getPrevSprite(Data::SpriteData* value)
        {
          return ((Device::Display::SpriteNode*)value)->prev;
        }

        INL VOID    deleteSprite(Data::SpriteData* value)
        {
          appendUndo((Device::Display::SpriteNode*)value, UndoDelete);
          closeUndo();
          removeNode((Device::Display::SpriteNode*)value);
        }
        INL VOID    selectSprite(Data::SpriteData* value)
        {
          selectNode((Device::Display::SpriteNode*)value);
        }
        INL VOID    deselectSprite(Data::SpriteData* value)
        {
          deselectNode((Device::Display::SpriteNode*)value);
        }
        INL VOID    insertSprite(
          Data::SpriteData* value,
          Data::SpriteData* after
          )
        {
          appendUndo(
            (Device::Display::SpriteNode*)value,
            UndoInsert);

          closeUndo();

          removeNode(
            (Device::Display::SpriteNode*)value);
          insertNode(
            (Device::Display::SpriteNode*)value,
            (Device::Display::SpriteNode*)after);
        }

        INL VOID    lockSelection()
        {
          setSelectionFlags(SPRITENODE_LOCKED, 0);
        }
        INL VOID    unlockSelection()
        {
          setSelectionFlags(0, SPRITENODE_LOCKED);
        }
        INL VOID    hideSelection()
        {
          setSelectionFlags(SPRITENODE_HIDDEN, 0);
        }
        INL VOID    showSelection()
        {
          setSelectionFlags(0, SPRITENODE_HIDDEN);
        }

        INL BOOL    isSpriteSelected(Data::SpriteData* value)
        {
          return value ? value->flags & SPRITENODE_SELECTED : FALSE;
        }
        INL BOOL    isSpriteHidden(Data::SpriteData* value)
        {
          return value ? value->flags & SPRITENODE_HIDDEN : FALSE;
        }
        INL BOOL    isSpriteLocked(Data::SpriteData* value)
        {
          return value ? value->flags & SPRITENODE_LOCKED : FALSE;
        }

        VOID          selectAll();
        VOID          deselectAll();
        VOID          alterSelection(const Geom::Bounds &value);
        VOID          deleteSelection();
        VOID          groupSelection();
        VOID          ungroupSelection();

        virtual BOOL  canUndo();
        virtual BOOL  canRedo();
        virtual BOOL  undo();
        virtual BOOL  redo();

        virtual BOOL  save(HANDLE iFile);
        virtual VOID  flush(Data::Resource* oResource);
        virtual VOID  setBufferSize(INT cx, INT cy, INT layers);
        virtual BOOL  recreate();

        SpriteEditor();
      };
    } // namespace Editor
  } // namespace Creator
} // namespace Retro

#endif
